Aspects of embodiments of the present invention relate to the field of computer security, and, more specifically, protection against code injection attacks.
Stack-based code injection attacks using buffer overflows are a common security risk in computer systems. In a buffer overflow attack, input data supplied by an attacker to a function (subroutine, method, procedure, or other callable unit) exceeds the size of the space allocated for that input (the “buffer”). If the computer system fails to check the size of the input data before writing the input data into the buffer, then the buffer “overflows” and data stored in portions of memory outside of the buffer can be corrupted.
FIG. 1 is a schematic diagram of a typical computer system. In a standard computer system, a central processing unit (CPU) 10 includes a program counter (PC) 12, a plurality of registers 14, an arithmetic/logic unit (ALU) 16, and a bus interface 18. The program counter 12 provides the address of the next instruction to be executed, the registers 14 store data and values currently being computed, and the ALU 16 performs computations on the data stored in the registers 14. Data is transferred into and out of the CPU 10 via the bus interface 18, which interfaces with an I/O bridge 20 to communicate with main memory 30 and other peripheral devices 40. While FIG. 1 illustrates a typical computer system, various other computer systems may be organized in different ways (for example, the I/O bridge 20 may be integrated into the CPU 10, or the CPU may include memory caches).
FIG. 2 is a schematic diagram illustrating a stack smashing buffer overflow attack. C. Cowan, et al., StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow Attacks 7 PROC. OF USENIX SEC. SYMP. (1998). As seen in FIG. 2, the stack 100 is stored in main memory 30 and grows in a downward direction (e.g., away from 0xFFFF and toward 0x0000). When a function call is made, a “Return Address” is pushed onto the stack and space is allocated for local variables, including a potentially attackable buffer at lower addresses. When input data is stored the buffer, the data (e.g., a text string from user input) grows in an upward direction (with increasing addresses). If the size of the input data exceeds the allocated size of the buffer, data located at higher addresses than the buffer can be overwritten with the supplied data. In FIG. 2, for example, the data overflowing the buffer could overwrite the Local Variables, the return address, and portions of the stack above the return address (which is generally the portion of the stack allocated to the calling function).
As seen in FIG. 2, the overflowing buffer can be used to insert executable attack code into the stack and to overwrite the return address with a pointer to the attack code. As such, when the attacked function exits, the processor jumps to and executes the attack code instead of the function that it was called from. The attack code can be used to gain root (or administrator) access to the machine by, for example, executing the command ‘exec(“sh”)’ to produce a root shell.
Generally, stack-based code injection attacks require: knowledge of the instruction set of the underlying architecture; knowledge of the location of one or more buffers on the stack; ability to inject code/data; and ability to redirect the control flow. An attacker can use the stack to return to the injected or other arbitrary code.
For an attacker to successfully craft a payload for a stack-based code injection attack, they need to be aware of the stack layout of the function they are attacking. This is because they need to change a return address in a very specific place on the stack to successfully gain control of the system.
Historically the layout of layout of these local variables (including any buffers) is controlled by the compiler, and each compiler handles them differently. However, it is generally not difficult for an attacker to determine (or guess) which compiler was used. The layout of these local variables has no influence on the execution of the function, because they are typically referenced in the machine code as an immediate offset from the stack pointer.
Most modern operating systems use some form of address space layout randomization (ASLR), which will randomize the location of the entire stack. See, e.g., O. Whitehouse, An Analysis of Address Space Layout Randomization on Windows Vista™, Symantec Advanced Threat Research. While this can provide some benefit, this concept requires support in the underlying operating system, and it can be turned off via operating system settings, thereby rendering system vulnerable when deactivated by an attacker or deactivated by a system administrator (due, for example, to compatibility problems between “legacy” software and ASLR).